---
id: appstartup
title: 3. 应用启动
sidebar_label: 3. 应用启动
---

## 3.1 `Startup` 类

`Startup` 类是 `ASP.NET Core` 应用程序启动默认调用的类，该类是在 `Program.cs` 中配置：

```cs {19} title="Fur.Web.Entry\Program.cs"
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace Fur.Web.Entry
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args)
        {
            return Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.Inject()
                        .UseStartup<Startup>();
                });
        }
    }
}
```

### 3.1.1 `Startup` 两个重要方法

`Startup` 默认有两个重要的方法:

- `ConfigureServices`：配置应用所需服务，在该方法中可以添加应用所需要的功能或服务
- `Configure`：配置应用请求处理管道

默认代码如下：

```cs {9,13} title="Fur.Web.Entry\Startup.cs"
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace Fur.Web.Entry
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
        }
    }
}
```

在这里，不打算详细讲 `Startup` 类的具体功能和作用。

:::note 了解更多

想了解更多 `Startup` 知识可查阅 [ASP.NET Core - Startup 类](https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/startup?view=aspnetcore-3.1) 章节。

:::

## 3.2 `AppStartup`

在 `Fur` 框架中，提供了更为灵活的 `Startup` 类配置方式，无需在 `Web 启用层` 中配置，可将配置放到任何项目层。

可能会有读者有疑问，为什么要多此一举呢？原因有几点：

- `Startup` 类默认和 `Web 应用层` 绑定在一起，这样就会导致如果我创建了新的 `Web 应用层`，`Startup` 又要重新配置
- 随着业务的增长，需要集成越来越多的第三方服务，这时候 `Startup` 类就会变得越来越臃肿，难以维护
- `Startup` 类无法与其他项目类型进行共用

所以，`Fur` 提供了更加灵活的配置方式：`AppStartup`。

### 3.2.1 如何配置 `AppStartup`

`AppStartup` 是一个抽象的空类，没有任何定义成员。正是因为这样，才提供更加灵活的配置方式。

### 3.2.2 `AppStartup` 约定

`AppStartup` 派生类只有两个小约定：

- 任何公开、非静态、返回值为 `void` 且方法第一个参数是 `IServiceCollection` 类型，那么他就是一个 `ConfigureServices` 方法
- 任何公开、非静态、返回值为 `void` 且方法第一个参数是 `IApplicationBuilder` 类型，第二个参数是 `IWebHostEnvironment` 类型，那么他就是一个 `Configure` 方法

所以，我们可以自由的编写方法，只要遵循约定即可，如：

```cs {5,7,12,18,23}
using Microsoft.Extensions.DependencyInjection;

namespace Fur.EntityFramework.Core
{
    public class MyStartup : AppStartup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDataValidation();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseSwagger();
        }

        // 可以随意定义名字和方法
        public void XXXXName(IServiceCollection services)
        {
        }

        // 可以随意定义名字和方法
        public void ZZZName(IApplicationBuilder app, IWebHostEnvironment env)
        {
        }
    }
}
```

### 3.2.3 `AppStartup` 配置顺序

默认情况下，`AppStartup` 配置顺序由所在程序集的名称进行正序调用，如果我们需要配置执行顺序，只需要在 `AppStartup` 派生类中贴 `[AppStartup(order)]` 特性即可。

`order` 数值越大，越在前面调用，如：

```cs {5}
using Microsoft.Extensions.DependencyInjection;

namespace Fur.EntityFramework.Core
{
    [AppStartup(10)]
    public class FirstStartup : AppStartup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
        }
    }
}
```

```cs {5}
using Microsoft.Extensions.DependencyInjection;

namespace Fur.EntityFramework.Core
{
    [AppStartup(9)]
    public class SecondStartup : AppStartup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
        }
    }
}
```

`FirstStartup` 会在 `SecondStartup` 之前调用。

### 3.2.4 `AppStartup` 方法调用顺序

`AppStartup` 方法调用顺序和方法的书写先后有关，越在前面的方法越先调用。

## 3.3 框架默认 `AppStartup` 实现

`Fur` 框架默认在每一个项目层中都创建了一个 `AppStartup` 派生类，方便大家配置。如：

- `FurStartup.cs`：`Fur` 框架核心启动类（通常无需配置）
- `FurApplicationStartup.cs`：业务服务启动类
- `FurCoreStartup.cs`：应用核心功能启动类
- `FurEntityFrameworkCoreStartup.cs`：数据库相关启动类
- `FurWebCoreStartup.cs`：`Web` 核心启动配置类

## 3.4 `Startup` 配置最佳实践

建议 `Web` 启动层的 `Startup.cs` 保持为空方法体，如：

```cs title="Fur.Web.Entry\Startup.cs"
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace Fur.Web.Entry
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
        }
    }
}
```

将所有 `Web 应用层` 配置迁移到 `FurWebCoreStartup.cs` 中，如：

```cs title="Fur.Web.Core\FurWebCoreStartup.cs"
using Fur.UnifyResult;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Fur.Web.Core
{
    [AppStartup(800)]
    public sealed class FurWebCoreStartup : AppStartup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCorsAccessor();

            services.AddSpecificationDocuments();
            services.AddControllers()
                       .AddDynamicApiControllers()
                       .AddDataValidation()
                       .AddFriendlyException()
                       .AddUnifyResult<RESTfulResult, RESTfulResultProvider>();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseCorsAccessor();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseSpecificationDocuments();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}
```

这样，后续更换 `Web 应用层` 也无需重新配置 `Startup.cs`

## 3.5 关于 `appsettings.json`

在默认情况下，`ASP.NET Core` 配置放在 `appsettings.json` 中配置，但是这样的方式和 `Startup.cs` 配置一样的道理，一旦我们更换了 `Web 应用层`，那么 `appsettings.json` 又要重新配置一次。

所以，`Fur` 框架提供了更加灵活的方式配置 `appsettings.json`，**只需要在任何项目层根目录下创建 `.json` 文件即可**

**`Fur` 框架最后会自动合并所有分散的配置文件。**

如我们在 `Fur.EntityFramework.Core` 层创建 `dbsettings.json` 配置数据库连接字符串，如：

```json title="Fur.EntityFramework.Core\dbsettings.json"
{
  "ConnectionStrings": {
    "DbConnectionString": "Server=localhost;Database=Fur;User=sa;Password=000000;MultipleActiveResultSets=True;",
    "Sqlite3ConnectionString": "Data Source=./Fur.db"
  }
}
```

**无需在 `appsettings.json` 中配置**，下面是 `appsettings.json` 默认代码:

```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.EntityFrameworkCore": "Information"
    }
  },
  "AllowedHosts": "*"
}
```

这样我们把配置文件分散在不同项目层之后，就可以实现共用和共享了。

## 3.6 神奇的 `Inject()`

`Inject()` 方法是 `Fur` 框架提供的最小侵入式的方法，可以让任何 `ASP.NET Core` Web 项目瞬间支持所有 `Fur` 框架特性。

我们创建了新的 `Web 项目`，只需要三个步骤即可：

- 添加 `Fur.Web.Core` 项目引用
- 在 `Program.cs` 中调用即可
- 将 `Startup.cs` 代码迁移到 `Fur.Web.Core` 项目的 `FurWebCoreStartup.cs` 中

如：

```cs {18} title="Fur.Web.Entry\Program.cs"
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace Fur.Web.Entry
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args)
        {
            return Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.Inject()
                        .UseStartup<Startup>();
                });
        }
    }
}
```

```cs title="Fur.Web.Entry\Startup.cs"
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace Fur.Web.Entry
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
        }
    }
}
```

:::important `Startup.cs` 代码迁移

只需要将 `ConfigureServices` 和 `Configure` 方法代码迁移到 `FurWebCoreStartup.cs` 中即可，而 `Startup.cs` 中两个方法留空即可。

:::

非常简单吧。我们后续创建任何 `MVC`，`RazorPages`，`Blazor` 项目只需要添加 `Fur.Web.Core` 引用和调用 `Inject()` 即可。

## 3.7 反馈与建议

:::note 与我们交流

给 Fur 提 [Issue](https://gitee.com/monksoul/Fur/issues/new?issue)。

:::
